// SPDX-License-Identifier: BSD-2.0+
/*
 * Copyright (C) 2019 Tang Haifeng <tanghaifeng-gz@loongson.cn>
 *
 * Basic support for the pwm module on loongson.
 */

#include <common.h>
#include <div64.h>
#include <pwm.h>
#include <asm/io.h>

#if defined(CONFIG_CPU_LOONGSON1)
#include <asm/ls1x.h>
#define LS_PWM0_BASE LS1X_PWM0_BASE
#define LS_PWM1_BASE LS1X_PWM1_BASE
#define LS_PWM2_BASE LS1X_PWM2_BASE
#define LS_PWM3_BASE LS1X_PWM3_BASE
#elif defined(CONFIG_LOONGSON2_SOC)
#include <asm/arch/ls2k.h>
#define LS_PWM0_BASE (CKSEG1ADDR(LS2X_PWM0_BASE))
#define LS_PWM1_BASE (CKSEG1ADDR(LS2X_PWM1_BASE))
#define LS_PWM2_BASE (CKSEG1ADDR(LS2X_PWM2_BASE))
#define LS_PWM3_BASE (CKSEG1ADDR(LS2X_PWM3_BASE))
#endif

#define REG_PWM_CNTR	0x00
#define REG_PWM_HRC	0x04
#define REG_PWM_LRC	0x08
#define REG_PWM_CTRL	0x0c

DECLARE_GLOBAL_DATA_PTR;

static void loongson_pwm_mux(void __iomem *base)
{
#if defined(CONFIG_LOONGSON2_SOC)
	{
	void __iomem *mux = (void __iomem *)CKSEG1ADDR(LS2X_MUX_BASE);
	int offset = 0;
	int id = ((u32)(base - LS_PWM0_BASE)) >> 4;

	switch (id) {
	case 0:
		offset = 12;
		break;
	case 1:
		offset = 13;
		break;
	case 2:
		offset = 14;
		break;
	case 3:
		offset = 15;
		break;
	default:
		return;
		break;
	}
	writel(readl(mux) | (1 << offset), mux);
	}
#endif
}

/* pwm_id from 0..7 */
static void __iomem *pwm_id_to_reg(int pwm_id)
{
	switch (pwm_id) {
	case 0:
		return (void __iomem *)LS_PWM0_BASE;
	case 1:
		return (void __iomem *)LS_PWM1_BASE;
	case 2:
		return (void __iomem *)LS_PWM2_BASE;
	case 3:
		return (void __iomem *)LS_PWM3_BASE;
	default:
		printf("unknown pwm_id: %d\n", pwm_id);
		break;
	}
	return NULL;
}

static int loongson_pwm_config(void __iomem *base, int duty_ns, int period_ns)
{
	unsigned long long tmp;
	unsigned long long period, duty;
	u32 reg;

	reg = readl(base + REG_PWM_CTRL);
	reg = reg & (~(1<<0));
	writel(reg, base + REG_PWM_CTRL);

	if (duty_ns < 0 || duty_ns > period_ns)
		return -1;

	tmp = (unsigned long long)gd->bus_clk * period_ns;
	do_div(tmp, 1000000000);
	period = tmp;

	tmp = (unsigned long long)period * duty_ns;
	do_div(tmp, period_ns);
	duty = period - tmp;

	writel(duty, base + REG_PWM_HRC);
	writel(period, base + REG_PWM_LRC);

	reg = readl(base + REG_PWM_CTRL);
	reg = reg | (1<<0);
	writel(reg, base + REG_PWM_CTRL);

	return 0;
}

int pwm_init(int pwm_id, int div, int invert)
{
	void __iomem *base = (void __iomem *)pwm_id_to_reg(pwm_id);
	u32 reg;

	if (!base)
		return -1;

	loongson_pwm_mux(base);

	reg = readl(base + REG_PWM_CTRL);

	if (invert)
		reg |= (1<<9);
	else
		reg &= (~(1<<9));

	writel(reg, base + REG_PWM_CTRL);

	return 0;
}

int pwm_config(int pwm_id, int duty_ns, int period_ns)
{
	void __iomem *base = (void __iomem *)pwm_id_to_reg(pwm_id);

	if (!base)
		return -1;

	loongson_pwm_config(base, duty_ns, period_ns);

	return 0;
}

int pwm_enable(int pwm_id)
{
	void __iomem *base = (void __iomem *)pwm_id_to_reg(pwm_id);
	u32 reg;

	if (!base)
		return -1;

	writel(0x00, base + REG_PWM_CNTR);
	reg = readl(base + REG_PWM_CTRL);
	reg = reg & (~(1<<3));
	writel(reg, base + REG_PWM_CTRL);

	return 0;
}

void pwm_disable(int pwm_id)
{
	void __iomem *base = (void __iomem *)pwm_id_to_reg(pwm_id);
	u32 reg;

	if (!base)
		return;

	reg = readl(base + REG_PWM_CTRL);
	reg = reg | (1<<3) | (1<<0);
	writel(reg, base + REG_PWM_CTRL);
}
